/* Microsoft Reference Implementation for TPM 2.0
 *
 *  The copyright in this software is being made available under the BSD License,
 *  included below. This software may be subject to other third party and
 *  contributor rights, including patent rights, and no such rights are granted
 *  under this license.
 *
 *  Copyright (c) Microsoft Corporation
 *
 *  All rights reserved.
 *
 *  BSD License
 *
 *  Redistribution and use in source and binary forms, with or without modification,
 *  are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this list
 *  of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice, this
 *  list of conditions and the following disclaimer in the documentation and/or
 *  other materials provided with the distribution.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ""AS IS""
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 *  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "Tpm.h"
#include "Policy_spt_fp.h"
#include "PolicySigned_fp.h"

#if CC_PolicySigned  // Conditional expansion of this file

/*(See part 3 specification)
// Include an asymmetrically signed authorization to the policy evaluation
*/
//  Return Type: TPM_RC
//      TPM_RC_CPHASH           cpHash was previously set to a different value
//      TPM_RC_EXPIRED          'expiration' indicates a time in the past or
//                              'expiration' is non-zero but no nonceTPM is present
//      TPM_RC_NONCE            'nonceTPM' is not the nonce associated with the
//                              'policySession'
//      TPM_RC_SCHEME           the signing scheme of 'auth' is not supported by the
//                              TPM
//      TPM_RC_SIGNATURE        the signature is not genuine
//      TPM_RC_SIZE             input cpHash has wrong size
/*
23.3.1 General Description

This command includes a signed authorization in a policy. The command ties the policy to a signing key by 
including the Name of the signing key in the policyDigest
If policySession is a trial session, the TPM will not check the signature and will update policySession→policyDigest 
as described in 23.2.3 as if a properly signed authorization was received, but no ticket will be produced.
If policySession is not a trial session, the TPM will validate auth and only perform the update if it is a valid 
signature over the fields of the command.
The authorizing entity will sign a digest of the authorization qualifiers: nonceTPM, expiration, cpHashA, and policyRef. 
The digest is computed as:
aHash ≔ HauthAlg(nonceTPM || expiration || cpHashA || policyRef) (13)
此命令在策略中包含签名授权。 该命令通过在 policyDigest 中包含签名密钥的名称将策略与签名密钥联系起来
如果 policySession 是一个试用会话，TPM 将不检查签名并将更新 policySession→policyDigest，如 23.2.3 中所述，就好像收到了正确签名的授权一样，但不会产生票据。
如果 policySession 不是试用会话，则 TPM 将验证身份验证，并且仅当它是命令字段上的有效签名时才执行更新。
授权实体将签署授权限定符的摘要：nonceTPM、expiration、cpHashA 和 policyRef。 摘要计算如下：
aHash ≔ HauthAlg(nonceTPM || expiration || cpHashA || policyRef) (13)

where
HauthAlg() the hash associated with the auth parameter of this command
NOTE 1 Each signature and key combination indicates the scheme and each scheme has an associated hash.
nonceTPM the nonceTPM parameter from the TPM2_StartAuthSession() response. If the authorization is not limited to this session, 
the size of this value is zero.
expiration time limit on authorization set by authorizing object. This 32-bit value is set to zero if the expiration time is not being set.
cpHashA digest of the command parameters for the command being approved using the hash algorithm of the policy session. Set to an 
Empty Digest if the authorization is not limited to a specific command.
NOTE 3 This is not the cpHash of this TPM2_PolicySigned() command.
policyRef an opaque value determined by the authorizing entity. Set to the Empty Buffer if no value is present.
NOTE 4 The nonceTPM, cpHashA, and policyRef qualifiers used to compute aHash use the TPM2B buffer but do not prepend the size
在哪里
HauthAlg() 与此命令的 auth 参数关联的散列
注 1：每个签名和密钥组合表示方案，每个方案都有一个关联的散列。
nonceTPM 来自 TPM2_StartAuthSession() 响应的 nonceTPM 参数。 如果授权不限于此会话，则此值的大小为零。
授权对象设置的授权过期时间限制。 如果未设置过期时间，则此 32 位值设置为零。
cpHashA 使用策略会话的散列算法批准的命令的命令参数的摘要。 如果授权不限于特定命令，则设置为 Empty Digest。
注 3 这不是此 TPM2_PolicySigned() 命令的 cpHash。
policyRef 由授权实体确定的不透明值。 如果没有值，则设置为空缓冲区。
注 4 用于计算 aHash 的 nonceTPM、cpHashA 和 policyRef 限定符使用 TPM2B 缓冲区，但不预先添加大小

EXAMPLE The computation for an aHash if there are no restrictions is:
aHash ≔ HauthAlg(00 00 00 0016)
which is the hash of an expiration time of zero.
The aHash is signed by the key associated with a key whose handle is authObject. The signature and signing parameters are combined to create the auth parameter.
The TPM will perform the parameter checks listed in 23.2.2
If the parameter checks succeed, the TPM will construct a test digest (tHash) over the provided
parameters using the same formulation as shown in equation (13) above.
If tHash does not match the digest of the signed aHash, then the authorization fails and the TPM shall return TPM_RC_POLICY_FAIL and make 
no change to policySession→policyDigest.
示例 如果没有限制，aHash 的计算是：
aHash ≔ HauthAlg(00 00 00 0016)
这是零过期时间的散列。
aHash 由与句柄为 authObject 的密钥关联的密钥签名。 signature 和 signing 参数组合在一起创建 auth 参数。
TPM 将执行 23.2.2 中列出的参数检查
如果参数检查成功，TPM 将根据提供的参数构造一个测试摘要 (tHash)
使用与上面等式 (13) 中所示相同的公式的参数。
如果 tHash 与签名的 aHash 的摘要不匹配，则授权失败，TPM 应返回 TPM_RC_POLICY_FAIL 并且不更改 policySession→policyDigest。

*/
TPM_RC
TPM2_PolicySigned(
    PolicySigned_In     *in,            // IN: input parameter list
    PolicySigned_Out    *out            // OUT: output parameter list
    )
{
    TPM_RC                   result = TPM_RC_SUCCESS;
    SESSION                 *session;
    TPM2B_NAME               entityName;
    TPM2B_DIGEST             authHash;
    HASH_STATE               hashState;
    UINT64                   authTimeout = 0;
// Input Validation
    // Set up local pointers
    // 获取待修改（hash extend）的策略会话
    session = SessionGet(in->policySession);    // the session structure

    // Only do input validation if this is not a trial policy session
    if(session->attributes.isTrialPolicy == CLEAR)
    {
        /*
        如果 policySession 不是试用会话，则 TPM 将验证身份验证，并且仅当它是命令字段上的有效签名时才执行更新。

        不是策略会话，则表明要通过策略会话的验证，签名策略顾名思义为验证签名。所以此处要提供证明表明待验证方拥有签名私钥。

        策略验证签名时，若提供了 expiration 则会进行验证，但是 policy digest update 没有该属性
        */
        authTimeout = ComputeAuthTimeout(session, in->expiration, &in->nonceTPM);

        // 先验证提供的参数的有效性，再验证签名
        result = PolicyParameterChecks(session, authTimeout,
                                       &in->cpHashA, &in->nonceTPM,
                                       RC_PolicySigned_nonceTPM,
                                       RC_PolicySigned_cpHashA,
                                       RC_PolicySigned_expiration);
        if(result != TPM_RC_SUCCESS)
            return result;
        /*
        授权实体会对 nonceTPM、expiration、cpHashA 和 policyRef 的摘要进行签名。
        先计算摘要：
        */
        // Re-compute the digest being signed
        /*(See part 3 specification)
        // The digest is computed as:
        //     aHash := hash ( nonceTPM | expiration | cpHashA | policyRef)
        //  where:
        //      hash()      the hash associated with the signed authorization
        //      nonceTPM    the nonceTPM value from the TPM2_StartAuthSession .
        //                  response If the authorization is not limited to this
        //                  session, the size of this value is zero.
        //      expiration  time limit on authorization set by authorizing object.
        //                  This 32-bit value is set to zero if the expiration
        //                  time is not being set.
        //      cpHashA     hash of the command parameters for the command being
        //                  approved using the hash algorithm of the PSAP session.
                            使用 PSAP 会话的散列算法批准的命令的命令参数的散列。
                            使用策略会话的散列算法批准的命令的命令参数的摘要。 如果授权不限于特定命令，则设置为 Empty Digest。
        //                  Set to NULLauth if the authorization is not limited
        //                  to a specific command.
        //      policyRef   hash of an opaque value determined by the authorizing
        //                  object.  Set to the NULLdigest if no hash is present.
                            由授权对象确定的不透明值的散列
        */
        // Start hash
        authHash.t.size = CryptHashStart(&hashState,
                                         CryptGetSignHashAlg(&in->auth));
        // If there is no digest size, then we don't have a verification function
        // for this algorithm (e.g. TPM_ALG_ECDAA) so indicate that it is a
        // bad scheme.
        if(authHash.t.size == 0)
            return TPM_RCS_SCHEME + RC_PolicySigned_auth;

        //  nonceTPM
        CryptDigestUpdate2B(&hashState, &in->nonceTPM.b);

        //  expiration
        CryptDigestUpdateInt(&hashState, sizeof(UINT32), in->expiration);

        //  cpHashA
        CryptDigestUpdate2B(&hashState, &in->cpHashA.b);

        //  policyRef
        CryptDigestUpdate2B(&hashState, &in->policyRef.b);

        //  Complete digest
        CryptHashEnd2B(&hashState, &authHash.b);

        // Validate Signature.  A TPM_RC_SCHEME, TPM_RC_HANDLE or TPM_RC_SIGNATURE
        // error may be returned at this point
        // 再使用公钥验证签名。如果验证通过，表明授权实体拥有私钥。
        result = CryptValidateSignature(in->authObject, &authHash, &in->auth);
        if(result != TPM_RC_SUCCESS)
            return RcSafeAddToResult(result, RC_PolicySigned_auth);
    }
// Internal Data Update
    // Update policy with input policyRef and name of authorization key
    // These values are updated even if the session is a trial session
    // 签名验证通过，将对应密钥(名称) 扩充到 策略会话的摘要值中。
    PolicyContextUpdate(TPM_CC_PolicySigned,
                        EntityGetName(in->authObject, &entityName),
                        &in->policyRef,
                        &in->cpHashA, authTimeout, session);
// Command Output
    // Create ticket and timeout buffer if in->expiration < 0 and this is not
    // a trial session.
    // NOTE: PolicyParameterChecks() makes sure that nonceTPM is present
    // when expiration is non-zero.
    if(in->expiration < 0
       && session->attributes.isTrialPolicy == CLEAR)
    {
        /*
        创建一个 TPM_ST_AUTH_SIGNED 票据
        证明 TPM  验证过 authName 对应的公钥的签名？
        */
        BOOL        expiresOnReset = (in->nonceTPM.t.size == 0);
        // Compute policy ticket
        authTimeout &= ~EXPIRATION_BIT;

        TicketComputeAuth(TPM_ST_AUTH_SIGNED, EntityGetHierarchy(in->authObject),
                          authTimeout, expiresOnReset, &in->cpHashA, &in->policyRef,
                          &entityName, &out->policyTicket);
        // Generate timeout buffer.  The format of output timeout buffer is
        // TPM-specific.
        // Note: In this implementation, the timeout buffer value is computed after 
        // the ticket is produced so, when the ticket is checked, the expiration
        // flag needs to be extracted before the ticket is checked.
        // In the Windows compatible version, the least-significant bit of the
        // timeout value is used as a flag to indicate if the authorization expires
        // on reset. The flag is the MSb.
        out->timeout.t.size = sizeof(authTimeout);
        if(expiresOnReset)
            authTimeout |= EXPIRATION_BIT;
        UINT64_TO_BYTE_ARRAY(authTimeout, out->timeout.t.buffer);
    }
    else
    {
        // Generate a null ticket.
        // timeout buffer is null
        out->timeout.t.size = 0;

        // authorization ticket is null
        out->policyTicket.tag = TPM_ST_AUTH_SIGNED;
        out->policyTicket.hierarchy = TPM_RH_NULL;
        out->policyTicket.digest.t.size = 0;
    }
    return TPM_RC_SUCCESS;
}

#endif // CC_PolicySigned